<!-- eslint-disable no-undef -->
<template>
  <div>
    <!-- 上传按钮 -->
    <div id="picker">
      <slot></slot>
    </div>
    <upload-list
      v-if="fileArr.length || echoFileArr.length"
      :file-arr="fileArr"
      :echo-file-arr="echoFileArr"
      :percentage="percentage"
      :pro-color="proColor"
      @cancel-file="cancelFile"
    ></upload-list>
  </div>
</template>

<script lang="ts" setup>
  import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
  // import WebUploader from 'webuploader';
  import { Message } from '@arco-design/web-vue';
  import md5 from 'js-md5';
  import { getUuid } from '@/utils/utils';
  import { getToken } from '@/utils/auth';
  import { setFusionBiz } from '@/api/interceptor';
  import UploadList from './upload-list/index.vue';
  import {
    queryUploadInfo,
    queryFileBlockList,
    queryUploadId,
    deleteFileInfo,
    mergeFile,
    queryGroupFileInfo,
  } from './api';

  const props = defineProps({
    // 文件组标识
    value: {
      type: String,
      default: '',
    },
    autoUpload: {
      type: Boolean,
      default: false,
    },
    // 允许上传的文件类型
    accept: {
      type: Array,
      default: null,
    },
    // 文件上传的最大数量
    fileNumLimit: {
      type: Number,
      default: 1,
    },
    // 文件大小限制
    fileSingleSizeLimit: {
      type: Number,
      default: null,
    },
    // 传递给后端的参数
    info: {
      type: Object,
      default() {
        return {};
      },
    },
    // 是否允许上传时选择多个文件
    isMultiple: {
      type: Boolean,
      default: false,
    },
    // 文件分片大小
    fileBlockSize: {
      type: Number,
      // default: 1024, // 1k
      default: 100 * 1024 * 1024, // 100M
    },
    // 是否禁用 TODO
    disable: {
      type: Boolean,
      default: false,
    },
  });

  const emit = defineEmits([
    'input',
    'lineChange',
    'uploadSuccess',
    'uploadCancel',
    'uploadError',
    'fileChange',
  ]);

  // window.navigator.onLine：H5新增对象，true表示在线
  const isLine = ref(navigator.onLine); // 当前网络状态
  const btnId = '#picker'; // 上传按钮id
  // eslint-disable-next-line no-undef
  const uploader = ref<WebUploader.Uploader>({}); // webUploader对象
  // const url =
  //   process.env.NODE_ENV === 'development'
  //     ? './api/sys-storage/bigFile'
  //     : '../api/sys-storage/bigFile'; // 文件上传地址

  const url = '/api/sys-storage/bigFile'; // 文件上传地址
  const paramArr = ref<any>({}); // 保存文件参数
  const fileBlockListObj = ref<any>({}); // 保存本次已上传的分片文件信息，文件合并时传递给后端
  const blockInfoObj = ref<any>({}); // 断点续传时暂存已上传的分片信息
  // eslint-disable-next-line no-undef
  const fileArr = ref<WebUploader.File>([]); // 已选文件数组
  // eslint-disable-next-line no-undef
  const echoFileArr = ref<WebUploader.File>([]); // 回显文件数组
  const percentage = ref<any>({}); // 上传进度
  const md5Obj = ref<any>({}); // 文件MD5信息
  const groupToken = ref('');
  const isPassSecond = ref(false); // 当前是否处于极速秒传状态
  const e9y = ref(''); // 指定文件上传的服务器

  // 格式化文件类型数组
  const getAccept = computed(() => {
    const arrs = props.accept;
    let exteensions = '';
    let mimeTypes = '';
    arrs.forEach((value) => {
      exteensions += `${value}, `;
      mimeTypes += `.${value}, `;
    });
    return {
      // 允许的文件格式
      exteensions,
      mimeTypes,
    };
  });

  // 获取当前网络状态
  const getLineStatus = (e: any) => {
    isLine.value = e.type === 'online';
  };
  // 获取文件key
  // eslint-disable-next-line no-undef
  const getFileKey = (fileObj: WebUploader.File) => {
    return `${fileObj.name}&${fileObj.lastModifiedDate}`;
  };
  // 获取文件的md5Key
  // eslint-disable-next-line no-undef
  const getMd5Key = (fileObj: WebUploader.File) => {
    // 文件名+文件大小+最后修改时间
    const key = fileObj.name + fileObj.size + fileObj.lastModifiedDate;

    return new Promise((res) => {
      const md5Key = md5(key);
      res(md5Key);
    });
  };

  // 上传进度条的颜色
  const proColor = computed(() => {
    return isLine.value ? 'rgb(64, 158, 255)' : 'rgb(144, 147, 153)';
  });

  // 重置队列
  const reset = () => {
    uploader.value.reset();
  };

  // 修改暂存的信息
  // eslint-disable-next-line no-undef
  const amendInfo = (fileObj: WebUploader.File, key: string) => {
    reset(); // 重置队列

    // eslint-disable-next-line no-undef
    fileArr.value.some((file: WebUploader.File, index: number) => {
      if (file.id === fileObj.id) {
        fileArr.value.splice(index, 1);
        return true; // 中断循环
      }
      return false;
    });

    delete percentage.value[key];
    delete md5Obj.value[key];
    delete fileBlockListObj.value[key];

    blockInfoObj.value = {};
  };

  /* 文件操作 */
  // 开始文件上传：从初始状态调用开始上传流程或从暂停状态调用，继续上传流程
  // eslint-disable-next-line no-undef
  const upload = (fileObj: WebUploader.File) => {
    if (!fileObj) {
      uploader.value.upload();
    } else {
      uploader.value.upload(fileObj.id);
    }
  };
  // 暂停文件上传：第一个参数为是否中断当前正在上传的文件，若第一个参数是文件，则只暂停指定文件
  // eslint-disable-next-line no-undef
  const stop = (fileObj: WebUploader.File) => {
    uploader.value.stop(true); // 暂停队列中所有的文件
    // this.uploader.stop(fileObj, true) //暂停指定文件：个人认为源码存在bug，无法正常使用，只能对单个分片的文件生效
  };
  // 重试上传
  // eslint-disable-next-line no-undef
  const retry = (fileObj: WebUploader.File) => {
    uploader.value.retry();
    // this.uploader.retry(fileObj)
  };

  // 取消（从上传列表中删除）并中断文件上传 - 主动取消
  // eslint-disable-next-line no-undef
  const cancelFile = (fileObj: WebUploader.File) => {
    const key = getFileKey(fileObj);

    uploader.value.cancelFile(fileObj);

    amendInfo(fileObj, key);

    const data = {
      objectName: paramArr.value[key].objectName,
      uploadId: paramArr.value[key].uploadId,
      endPointKey: e9y.value,
    };

    // 通知后台清空对应文件信息
    deleteFileInfo(data);

    emit('uploadCancel', fileObj);
  };
  // 在队列中移除文件，默认标记文件状态为已取消，若第二个参数为true,则会从队列中移除
  // eslint-disable-next-line no-undef
  const removeFile = (fileObj: WebUploader.File, bool: boolean) => {
    if (bool) {
      uploader.value.removeFile(fileObj.id, bool);
    } else {
      uploader.value.removeFile(fileObj.id);
    }
  };

  watch(
    () => isLine,
    (n) => {
      if (n) {
        emit('lineChange', true);
        upload(null); // 重新上传

        // this.getFiles()
      } else {
        emit('lineChange', false);
        stop(null); // 暂停上传

        // this.getFiles()
      }
    }
  );

  // 初始化WebUpload组件，注册监听事件
  const initWebUpload = () => {
    // eslint-disable-next-line no-undef
    uploader.value = WebUploader.create({
      auto: props.autoUpload, // 选择文件后自动上传
      // swf: './static/Uploader.swf', //swf文件路径
      server: url, // 文件上传地址
      pick: {
        id: btnId, // 文件上传的按钮
        multiple: props.isMultiple, // 是否允许上传时选择多个文件
        // innerHTML: '上传' //指定按钮文字
      },
      dnd: btnId,
      accept: props.accept ? getAccept : '', // 允许上传的文件格式
      // threads: 1, //分片上传并发数 默认值：3
      fileNumLimit: props.fileNumLimit, // 限制上传文件总数
      fileSingleSizeLimit: props.fileSingleSizeLimit, // 限制单个上传文件的大小
      chunked: true, // 分片上传
      chunkSize: props.fileBlockSize, // 分片大小 插件默认值：5M
      // chunkRetry: false, //当前分片的最大重传次数 默认值：2
      // prepareNextFile: true, //上传当前文件时预处理下一文件 默认值：false
      duplicate: true, // 重复上传
      formData: { e9y: e9y.value }, // 发送请求时携带的参数 默认值：{}
    });

    // 当文件被加入到队列前触发，进行校验
    // eslint-disable-next-line no-undef
    uploader.value.on('beforeFileQueued', (fileObj: WebUploader.File) => {
      const key = getFileKey(fileObj);

      if (percentage.value[key]) {
        Message.error('相同文件请勿重复上传');
        emit('uploadError', 1, fileObj, '相同文件请勿重复上传');
        return false;
      }
      if (
        fileArr.value.length + echoFileArr.value.length >=
        props.fileNumLimit
      ) {
        Message.error('上传文件的数量超出限制');
        emit('uploadError', 1, fileObj, '上传文件的数量超出限制');
        return false;
      }
      if (fileObj.size === 0) {
        Message.error('请勿上传空文件');
        emit('uploadError', 1, fileObj, '请勿上传空文件');
        return false;
      }
      // if (fileObj.size < 5242880) {
      //   Message.error('上传文件大小需大于5MB');
      //   emit('uploadError', 1, fileObj, '上传文件大小需大于5MB');
      //   return false;
      // }

      fileArr.value.push({ ...fileObj, description: '' });
      percentage.value[key] = 1;
      return true;
    });

    // 当文件被加入到队列后触发
    // uploader.value.on('fileQueued', (file: WebUploader.File) => {
    //   console.log('fileQueued', file);
    // });

    // 文件开始上传前触发，一个文件只会触发一次
    // eslint-disable-next-line no-undef
    uploader.value.on('uploadStart', (file: WebUploader.File) => {
      console.log('uploadStart', file);
    });

    // 当前文件的分片在发送前触发
    uploader.value.on(
      'uploadBeforeSend',
      // eslint-disable-next-line no-undef
      (fileObj: WebUploader.File, data, headers) => {
        console.log('uploadBeforeSend 分片发送前', fileObj);
        headers['Fusion-Auth'] = getToken() || '';
        headers['Fusion-Biz'] = setFusionBiz() || '';
        console.log('fusion_biz: ', setFusionBiz());
      }
    );

    // 上传过程中触发，更新上传进度
    uploader.value.on(
      'uploadProgress',
      // eslint-disable-next-line no-undef
      (fileObj: WebUploader.File, progress: number) => {
        console.log('uploadProgress', fileObj);
        const key = getFileKey(fileObj);

        const num = Math.round(progress * 100);
        if (percentage.value[key] && percentage.value[key] > num) {
          return;
        }
        if (num === 100) {
          percentage.value[key] = 99;
        } else percentage.value[key] = num;
      }
    );

    // 当分片文件上传到服务端响应后触发
    // eslint-disable-next-line no-undef
    uploader.value.on('uploadAccept', (fileBlockObj: WebUploader.File, res) => {
      console.log('uploadAccept', fileBlockObj);
      if (!res.status) {
        const fileObj = fileBlockObj.file; // 获取当前分片的主文件
        const key = getFileKey(fileObj);

        amendInfo(fileObj, key); // 修改暂存的信息

        emit('uploadError', 2, fileObj, res);
      }
    });

    // 完整文件上传成功时触发：通知后台进行文件合并
    uploader.value.on(
      'uploadSuccess',
      // eslint-disable-next-line no-undef
      (fileObj: WebUploader.File, response) => {
        console.log('uploadSuccess', fileObj);
        // 极速秒传时不需要通知合并
        if (isPassSecond.value) {
          isPassSecond.value = false;
          reset(); // 重置队列
          return;
        }

        const key = getFileKey(fileObj);

        // 容错：处理上传进度误差
        if (percentage.value[key] < 100) {
          percentage.value[key] = 100;
        }

        const data = {
          e9y: e9y.value,
          md5: md5Obj.value[key],
          list: fileBlockListObj.value[key],
          uploadId: paramArr.value[key].uploadId,
          objectName: paramArr.value[key].objectName,
          fileName: fileObj.name,
          fileSize: fileObj.size,
          groupToken: groupToken.value,
          info: props.info,
        };
        // 合并文件
        mergeFile(data)
          .then((res) => {
            if (res.status) {
              // 设置fileToken
              // eslint-disable-next-line no-undef
              fileArr.value.some((file: WebUploader.File, index: number) => {
                if (file.id === fileObj.id) {
                  fileArr.value[index].fileToken = res.data.fileToken;
                  return true; // 中断循环
                }
                return false;
              });

              delete fileBlockListObj.value[key];

              blockInfoObj.value = {};

              reset(); // 重置队列

              emit('uploadSuccess', fileObj, res);
            } else {
              amendInfo(fileObj, key);
              emit('uploadError', 3, fileObj, res);
            }
          })
          .catch((error) => {
            emit('uploadError', 3, fileObj, error);
          });
      }
    );

    // 文件上传出错时触发
    uploader.value.on(
      'uploadError',
      // eslint-disable-next-line no-undef
      (fileObj: WebUploader.File, reason: any) => {
        console.log('uploadError', fileObj, reason);
        // 放过断网引起的error
        if (reason && reason.statusText === 'abort') {
          return;
        }

        const key = getFileKey(fileObj);

        amendInfo(fileObj, key); // 修改暂存的信息

        emit('uploadError', 2, fileObj, reason);
      }
    );

    // 内置错误类型
    uploader.value.on('error', (type: string) => {
      console.log('error', type);
      let errorMessage;

      if (type === 'F_EXCEED_SIZE') {
        errorMessage = `上传文件的大小不能超过${
          props.fileSingleSizeLimit / (1024 * 1000)
        }M`;
      } else if (type === 'Q_EXCEED_NUM_LIMIT') {
        errorMessage = '文件并发上传数量超出限制';
      } else if (type === 'Q_TYPE_DENIED') {
        errorMessage = '上传文件的类型超出限制';
      } else {
        errorMessage = `文件上传出错，请检查后重新上传`;
      }

      const fileObj = fileArr.value.pop(); // 删除已选文件数组中新增的文件

      const key = getFileKey(fileObj);
      delete percentage.value[key];

      emit('uploadError', 1, null, errorMessage);
    });

    // 完整文件上传完成时触发：无论上传成功或上传失败
    // eslint-disable-next-line no-undef
    uploader.value.on('uploadComplete', (file: WebUploader.File, response) => {
      console.log('uploadComplete', file);
    });
  };

  // 初始化文件组信息
  const initGroupData = () => {
    if (!props.value) {
      groupToken.value = getUuid(16, 32);
      emit('input', groupToken.value);
      return;
    }

    groupToken.value = props.value;
    const data = {
      g9s: new Array(groupToken.value),
    };
    // 获取已上传的文件组信息
    queryGroupFileInfo(data).then((res) => {
      if (res.status && res.data) {
        // eslint-disable-next-line no-undef
        res.data.forEach((file: WebUploader.File) => {
          echoFileArr.value.push(file);
        });
      }
    });
  };
  initGroupData();

  // 获取文件信息（供父组件调用）
  const getFilesInfo = () => {
    return fileArr.value;
  };
  // 手动上传（供父组件调用）
  const submit = () => {
    uploader.value.upload();
  };

  defineExpose({
    getFilesInfo,
    submit,
  });

  watch(
    fileArr,
    () => {
      emit('fileChange', fileArr.value);
    },
    { deep: true }
  );

  onMounted(() => {
    window.addEventListener('online', getLineStatus); // 网络由异常状态到正常状态
    window.addEventListener('offline', getLineStatus); // 网络有正常状态到异常状态

    // 文件开始发送前触发，WebUploader会等待此异步操作完成后，再上传文件(主要用于处理异步操作)
    // eslint-disable-next-line no-undef
    WebUploader.Uploader.register({
      'name': 'bsf',
      // eslint-disable-next-line no-undef
      'before-send-file': (fileObj: WebUploader.File) => {
        console.log('before-send-file');
        // eslint-disable-next-line no-undef
        const deferred = WebUploader.Deferred();

        const key = getFileKey(fileObj);

        getMd5Key(fileObj)
          .then((md5Value) => {
            md5Obj.value[key] = md5Value;

            const data = {
              fileName: fileObj.name,
              fileSize: fileObj.size,
              md5: md5Value,
              g9s: groupToken.value,
            };
            // 获取当前文件上传情况（秒传接口）
            queryUploadInfo(data)
              .then((res) => {
                if (!res.status) {
                  // 异常状态：文件类型不符合后端限制等情况 TODO
                  emit('uploadError', 4, fileObj, res);
                  amendInfo(fileObj, key);
                  deferred.reject(); // 注意：会触发webUpload的uploadError
                } else if (res.data.resultType === 0) {
                  // 极速秒传 TODO
                  isPassSecond.value = true;
                  percentage.value[key] = 99;

                  fileArr.value.some(
                    // eslint-disable-next-line no-undef
                    (file: WebUploader.File, index: number) => {
                      if (file.id === fileObj.id) {
                        fileArr.value[index].fileToken =
                          res.data.sysOssVO.fileToken;
                        return true; // 中断循环
                      }
                      return false;
                    }
                  );

                  setTimeout(() => {
                    percentage.value[key] = 100;
                    emit('uploadSuccess', fileObj, '极速秒传成功');
                  }, 500);

                  uploader.value.skipFile(fileObj); // 跳过当前文件
                  deferred.resolve();
                } else if (res.data.resultType === 2) {
                  // 断点续传
                  const params = {
                    objectName: res.data.bigFileVO.objectName,
                    uploadId: res.data.bigFileVO.uploadId,
                    endPointKey: e9y.value,
                  };

                  paramArr.value[key] = {
                    objectName: res.data.bigFileVO.objectName,
                    uploadId: res.data.bigFileVO.uploadId,
                  };

                  // 完整文件 <= 分片大小时，需要手动添加chunk参数
                  if (fileObj.size <= props.fileBlockSize) {
                    uploader.value.options.formData.chunk = 0;
                  }

                  // 获取当前文件已上传的分片列表
                  queryFileBlockList(params).then((response) => {
                    if (response.status) {
                      // 暂存已上传分片信息
                      response.data.forEach((item: any) => {
                        blockInfoObj.value[
                          item.etag
                        ] = `${item.size}-${item.partNumber}`;
                      });

                      deferred.resolve();
                    } else {
                      emit('uploadError', 4, fileObj, response);
                      amendInfo(fileObj, key);
                      deferred.reject();
                    }
                  });
                } else {
                  // 普通上传 resultType为1
                  const params = {
                    fileName: fileObj.name,
                    md5: md5Value,
                    e9y: e9y.value,
                  };

                  // 获取当前上传文件唯一标识
                  queryUploadId(params)
                    .then((response) => {
                      if (response.status) {
                        paramArr.value[key] = {
                          objectName: response.data.objectName,
                          uploadId: response.data.uploadId,
                        };

                        // 完整文件 <= 分片大小时，需要手动添加chunk参数
                        if (fileObj.size <= props.fileBlockSize) {
                          uploader.value.options.formData.chunk = 0;
                        }

                        deferred.resolve();
                      } else {
                        emit('uploadError', 4, fileObj, response);
                        amendInfo(fileObj, key);
                        deferred.reject();
                      }
                    })
                    .catch((error) => {
                      emit('uploadError', 4, fileObj, error);
                      amendInfo(fileObj, key);
                      deferred.reject();
                    });
                }
              })
              .catch((error) => {
                emit('uploadError', 4, fileObj, error);
                amendInfo(fileObj, key);
                deferred.reject();
              });
          })
          .catch((error) => {
            emit('uploadError', 2, fileObj, '文件的md5Key计算出错');
            amendInfo(fileObj, key);
            deferred.reject();
          });

        return deferred.promise(); // 继续执行分片上传
      },
    });

    // eslint-disable-next-line no-undef
    WebUploader.Uploader.register({
      'name': 'bs',
      // 分片文件在发送前触发：检查当前分片是否已经上传 断点续传
      'before-send': (fileBlock: any) => {
        console.log('before-send', fileBlock);
        // eslint-disable-next-line no-undef
        const deferred = WebUploader.Deferred();
        const key = getFileKey(fileBlock.file);

        uploader.value.options.formData.uploadId = paramArr.value[key].uploadId; // 文件唯一标识
        uploader.value.options.formData.objectName =
          paramArr.value[key].objectName;

        // 计算分片md5值
        uploader.value
          .md5File(fileBlock.blob)
          .then((md5Value: string) => {
            if (!fileBlockListObj.value[key]) {
              fileBlockListObj.value[key] = new Array({
                etag: md5Value,
                number: fileBlock.chunk + 1,
              });
            } else {
              fileBlockListObj.value[key].push({
                etag: md5Value,
                number: fileBlock.chunk + 1,
              });
            }

            if (blockInfoObj.value[md5Value]) {
              deferred.reject(); // 跳过当前分片 注意：不会触发webUpload的uploadError
            } else {
              deferred.resolve();
            }
          })
          .catch((error) => {
            emit('uploadError', 2, fileBlock, '分片的md5计算出错');
            amendInfo(fileBlock, key);
          });

        return deferred.promise(); // 继续执行分片上传
      },
    });

    // 初始化webUploader
    initWebUpload();
  });

  onUnmounted(() => {
    console.log('onUnmounted');
    window.removeEventListener('online', getLineStatus);
    window.removeEventListener('offline', getLineStatus);

    // eslint-disable-next-line no-undef
    WebUploader.Uploader.unRegister('bsf');
    // eslint-disable-next-line no-undef
    WebUploader.Uploader.unRegister('bs');
  });
</script>

<style lang="less" scoped>
  :deep(.webuploader-container) {
    width: 100%;
    position: relative;
    display: inline-block;
  }

  :deep(.webuploader-element-invisible) {
    display: none;
  }
</style>
